package gamebackend;

import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;

import framework.Color;
import framework.GraphicsWrapper;
import framework.ImageTinter;
import framework.ImageWrapper;
import framework.KPool;
import framework.KPool.PoolObjectFactory;
import framework.ResourcesWrapper;


public abstract class AbstractShip implements Ship, PoolObjectFactory<Explosion> {

	
	
	/**
	 * Autopilot policy, to be used when the user is not pressing keys.
	 * @author Jonathan Caddey
	 *
	 */
	protected static enum AutoPilot {
		OFF,
		
		/**
		 * A target has been set for the ship to move to.
		 */
		POSITION,
		
		/**
		 * A desired velocity has been set for the ship to achieve.
		 */
		VELOCITY;
	}
	
	
	protected static enum ShipState {
		/**
		 * The ship operates normally
		 */
		ALIVE,
		
		/**
		 * The ship has taken critical damage and will explode if not repaired soon.
		 */
		DYING,
		
		/**
		 * The ship has been destroyed and the game is over.
		 */
		DEAD;
	}
	
	/**
	 * To show damage was taken, the ship flashes red.  These
	 * tinters which color the ship are cached.
	 */
	int TINTER_COUNT = 7;
	
	/**
	 * Maximum decoys.
	 */
	private static final int MAX_DECOYS = 10;
	
	/*
	 * For now, this is not final. This is so the test user can modify the speed
	 * of _all_ ships (including those used by FlightPaths) with one key press.
	 */
	private static final double[] MOVE_VALUES = {
		//Mine
		// Forward Speed: 
		0.08762347487034815
		//.18
		,// Sideways Speed: 
		0.21500082851239669
		,// Sideways Acceleration: 
		7.114723493386153E-4

		,// Sideways Deceleration: 
		//1.185109155292889E-4,
		7.114723493386153E-4,
		1000
		
		// Mine, Fast
//		// Forward Speed: 
//		0.44289025000000043
//		,// Sideways Speed: 
//		0.3085802500000003
//		,// Sideways Acceleration: 
//		0.0018453760416666683
//		,// Sideways Deceleration: 
//		0.0029719965688645867,
		// 1000
		
//		//Mom's
//		// Forward Speed: 
//		0.08762347487034815
//		,// Sideways Speed: 
//		0.21500082851239669
//		,// Sideways Acceleration: 
//		1.8735290615543135E-4
//		,// Sideways Deceleration: 
//		1.185109155292889E-4,
		// 1000
		
		
	};
	/**
	 * The slowest the ship will move upwards.
	 */
	private static double MAX_SPEED = 0.44289025000000043;
	private static double MIN_Y_SPEED = MOVE_VALUES[0];
	private static double MAX_X_SPEED = MOVE_VALUES[1];
	/**
	 * Max sideways acceleration
	 */
	private static double ACCELERATION = MOVE_VALUES[2];
	private static double DECAY = MOVE_VALUES[3];
	private static boolean INVULNERABLE = false;
	private static boolean SPEED_INCREASES = true;
	/**
	 * How tall the ship is for rendering purposes.
	 */
	private static final int HEIGHT = 20;
	private static final int WIDTH = 25;//15;



	private static final double BOOST_SPEED = 110 / 1000.0; // was 110, 175
	private static final double Y_DECELERATION = .00005;
	private static final double Y_ACCELERATION = .00006;
	/**
	 * How frequently the minimum speed is updated based on the game time.
	 */
	private static final int TIMED_DIFFICULTY_INTERVAL = 1000;
	
	// TODO these next few deal with collisions and could be possibly unified somehow.
	/**
	 * The multiplier for x velocity when the ship hits the side of a block.
	 */
	private static final double COLLISION_RATIO_X = .2;
	/**
	 * The multiplier for y velocity when the ship hits the front of a block.
	 */
	private static final double COLLISION_RATIO_Y = .6; // was .7
	/**
	 * The horizontal speed added to the ship when it's hit by an envirolaser.
	 */
	private static final double ENVIRO_LASER_IMPACT = 300 / 1000.0;
	
	/**
	 * How many miliseconds the player has to recover from a collision before further collisions deal more damage.
	 */
	private static int RECOVERY_TIME = (int) MOVE_VALUES[4];

	
	private static final int[] BOOSTS_PER_TIER = {3, 3, 3, 3, 4};
	protected static final int TIERS = BOOSTS_PER_TIER.length;
	private final ShipListener my_listener;
	private final ImageWrapper my_image = getImage();
	private double my_x;
	private double my_y = 0;
	private double my_xVelo;
	private double my_yVelo = 0;
	private double my_acceleration;
	private boolean my_leftOn, my_rightOn;
	private double my_targetX;
	/**
	 * Used for when autopilot is based on target and the ship moves outside
	 * of the users control.  This way the ship won't constantly head
	 * back towards the source of damage if something throws the ship aside.
	 */
	private double my_targetOffset;
	private double my_targetVelocity;
	private AutoPilot my_autoPilot = AutoPilot.OFF;
	private double my_autoPilotSpeed;
	private int my_autoPilotSmoothness;
	private int my_boosts;
	private final List<ShipDecoy> my_decoys;
	/**
	 * The decoy the ship will try to teleport to if it takes damage.
	 */
	private int my_decoyIndex;
	private int my_safeFrames;
	/**
	 * Collecting consecutive boosts without taking
	 * damage grants a new boost tier which comes with rewards.
	 * Taking damage can drop the tier down to 0.
	 */
	private int my_boostTier;
	/**
	 * Whether a boost was picked up since missing a boost.
	 * This ensures it takes missing 2 boosts to drop a tier
	 * in cases where the Ship just reaches a new tier and has 0
	 * boosts and would otherwise drop a tier by missing another
	 * boost.
	 */
	private boolean my_boostSinceMiss;
	
	
	private int my_recoveryTime;
	private int my_damage;
	/**
	 * How long this ship has been alive.  Used for minimum speed purposes.
	 */
	private int my_time;
	/**
	 * How frequently the minimum speed is updated based on the game time.
	 */
	private int my_timedDifficultyTimer;
	private double my_minSpeed;
	
	
	private final List<Explosion> my_explosions;
	private final KPool<Explosion> my_explosionPool;
	/**
	 * Time before the ship emits another small explosion
	 */
	private int my_timeToExplosion;
	
	/**
	 * How long the ship survives with 0 health before being destroyed.
	 */
	private int my_dyingTime = 0;
	private ShipState my_state = ShipState.ALIVE;
	
	/**
	 * Used for when the ship takes damage and flashes red.
	 */
	private ImageTinter[] my_tints;
	/**
	 * Colors the decoys from the decoy powerup.
	 */
	private ImageTinter my_decoyTinter;
	
	
	protected AbstractShip(final ShipListener the_listener, final double the_x, double the_y) {
		
		my_explosions = new ArrayList<Explosion>();
		my_decoys = new ArrayList<ShipDecoy>();
		my_explosionPool = new KPool<Explosion>(this, 5);
		my_x = the_x;
		my_y = the_y;
		my_listener = the_listener;
		
	}
	
	
	protected AbstractShip(final Ship the_ship) {
		
		my_x = the_ship.getXPosition();
		my_y = the_ship.getYPosition();
		my_xVelo = the_ship.getXVelocity();
		my_yVelo = the_ship.getYVelocity();
		my_acceleration = the_ship.getXAcceleration();
		my_time = the_ship.getTime();
		my_boosts = the_ship.getBoostCount();
		
		// these are null because ships made this way
		// are to be used in flightpaths which don't
		// have any rendering.
		my_listener = null;
		my_explosions = null;
		my_explosionPool = null;
		my_decoys = null;
	}
	
	
	public void step(int timeDelta, double cameraYChange) {
		if (timeDelta == 0) {
			return;
		}
		
		explosionStep(timeDelta, cameraYChange);
		if (my_decoys != null) {
			for (ShipDecoy s : my_decoys) {
				s.step(timeDelta);
			}
		}
		
		
		my_time += timeDelta;
		my_timedDifficultyTimer -= timeDelta;
		if (my_timedDifficultyTimer < 0) {
			my_timedDifficultyTimer += TIMED_DIFFICULTY_INTERVAL;
			updateMinSpeed();
		}
		
		autoPilotStep(timeDelta);
		
		
		// decay (when the player is not going left or right, the ship decelerates)
		if (my_autoPilot == AutoPilot.OFF && !my_leftOn && !my_rightOn) {
			my_acceleration = -getSidewaysAcceleration() * Math.signum(my_xVelo);
			if (Math.abs(my_acceleration * timeDelta) > Math.abs(my_xVelo)) {
				my_acceleration = -my_xVelo / timeDelta;
			}
		}
		
		// enforce max speed
		final double maxSpeed = my_autoPilot == AutoPilot.POSITION ? my_autoPilotSpeed : getSidewaysSpeed();
		final double newVelo = my_xVelo + timeDelta * my_acceleration;
		if (newVelo > maxSpeed) {
			my_acceleration = (maxSpeed - my_xVelo) / timeDelta;
		} else if (newVelo < -maxSpeed) {
			my_acceleration = (-maxSpeed - my_xVelo) / timeDelta;
		}
		
		// physics stuff
		my_xVelo += my_acceleration * timeDelta;
		my_x += getXPositionChange(timeDelta);
		my_y += cameraYChange - getYPositionChange(timeDelta);
		
		
		my_yVelo -= .5 * Y_DECELERATION * timeDelta * timeDelta;
		if (my_yVelo < my_minSpeed) {
			my_yVelo += .5 * Y_ACCELERATION * timeDelta * timeDelta;
			if (my_yVelo > my_minSpeed) {
				my_yVelo = my_minSpeed;
			}
		}
		
		if (my_recoveryTime > 0) {
			my_recoveryTime -= Math.min(timeDelta, my_recoveryTime);
		} else if (my_state == ShipState.DYING) {
			my_state = ShipState.DEAD;
			updateMinSpeed();
			
			final Explosion e = my_explosionPool.newObject();
			e.initialize(my_x, my_y, 50);
			my_explosions.add(e);
			
			my_listener.onShipDestroyed();
		}
		
		// decoy stuff
		if (my_decoyIndex > 0) {
			my_safeFrames++;
			if (my_safeFrames >= 2) {
				my_targetOffset += my_decoys.get(my_decoyIndex - 1).getXPosition();
//				final ShipDecoy removed = my_decoys.get(my_decoyIndex - 1);
//				
//				for (ShipDecoy decoy : my_decoys) {
//					final double x = decoy.getXPosition();
//					final double y = decoy.getYPosition();
//					decoy.setXPosition(x - removed.getXPosition());
//					decoy.setYPosition(y - removed.getYPosition());
//					
//				}
				my_decoys.remove(my_decoyIndex - 1);
				my_autoPilot = AutoPilot.OFF;
				my_recoveryTime += 150;
				my_decoyIndex = 0;
			}
			
		}
	}
	
	private void explosionStep(int timeDelta, double cameraYChange) {
		if (my_explosions != null) {
			for (Iterator<Explosion> it = my_explosions.iterator(); it.hasNext();) {
				final Explosion e = it.next();
				e.step(timeDelta, cameraYChange);
				if (!e.isAlive()) {
					it.remove();
					my_explosionPool.free(e);
				}
			}
		
			if (getHealth() == 1 || my_state == ShipState.DYING) {
				my_timeToExplosion -= timeDelta;
				if (my_timeToExplosion < 0) {
					final double size;
					final int count;
					if (my_state == ShipState.DYING) {
						final double ratio = Math.max(0, (double)my_recoveryTime / my_dyingTime);
						my_timeToExplosion += 150 + 100 * ratio;
						count = 1 + (int)(4 * (1 - ratio));
						size = 30 + 20 * (1 - ratio);
					} else {
						my_timeToExplosion += 750;//was 900
						count = 1;
						size = 25;
					}
					for (int i = 0; i < count; i++) {
						final Explosion e = my_explosionPool.newObject();
						e.initialize(my_x + Math.random() * 30 - 15, my_y + Math.random() * 30 - 30, (Math.random() * .5 + .75) * size);
						my_explosions.add(e);
					}
				}
			}
		}
	}
	
	private void autoPilotStep(int timeDelta) {
		if (my_autoPilot == AutoPilot.POSITION && my_x != my_targetX) {
			int direction = my_x > my_targetX ? -1 : 1;
			if (my_xVelo * direction < 0) {
				// heading away from target
				if (getSidewaysAcceleration() > Math.abs(my_xVelo) / timeDelta) {
					// able to get 0 velocity
					my_acceleration = -my_xVelo / timeDelta;
				} else {
					// unable to get 0 velocity, so go full force towards 0
					my_acceleration = getSidewaysAcceleration() * direction;
				}
			} else {
				// heading towards target
				// the next velocity if the ship keeps accelerating (plus a little extra)
				double newVelo = my_xVelo + direction * getSidewaysAcceleration() * timeDelta;// * 1.5;
				if (Math.abs(newVelo) > getSidewaysSpeed()) {
					newVelo = getSidewaysSpeed() * direction;
				}
				// where the ship must start slowing down; otherwise it can keep speeding up
				double brakeAt = -direction * newVelo * newVelo / 2 / getSidewaysAcceleration() * my_autoPilotSmoothness + my_targetX;
				
				if (brakeAt * direction > my_x * direction) {
					// the break position is past the current position, so keep speeding up
					my_acceleration = getSidewaysAcceleration() * direction;
				} else {
					// the ship will pass the break position if it keeps accelerating, so slow down.
					my_acceleration = -getSidewaysAcceleration() * direction;
					
				}
				if (Math.abs(my_xVelo + my_acceleration * timeDelta) > Math.abs(my_x - my_targetX) || Math.abs(my_x - my_targetX) < 1) {
					// this is cheating, but easier than doing proper calculations
					my_acceleration = 0;
					my_xVelo = 0;
					my_x = my_targetX;
				}
				
			}

		} else if (my_autoPilot == AutoPilot.VELOCITY) {
			final double difference = my_targetVelocity - my_xVelo;
			if (Math.abs(difference) <= getSidewaysAcceleration() * timeDelta) {
				my_acceleration = difference / timeDelta;
			} else {
				my_acceleration = getSidewaysAcceleration() * Math.signum(difference);
			}
		}
	}
	
	
	public double getYPositionChange(int timeDelta) {
		return my_yVelo * timeDelta; // TODO shouldn't this have acceleration component?
	}
	public double getYVelocity() {
		return my_yVelo;
	}
	public double getXPositionChange(int timeDelta) {
		return my_xVelo * timeDelta + my_acceleration / 2 * timeDelta * timeDelta;
	}
	public double getXVelocity() {
		return my_xVelo;
	}

	public double getXPosition() {
		return my_x;
	}
	public void setXPosition(final double the_newX) {
		my_x = the_newX;
	}
	public double getYPosition() {
		return my_y;
	}
	public void setYPosition(final double the_newY) {
		my_y = the_newY;
	}
	public void leftThruster(boolean on) {
		my_leftOn = on;
		my_autoPilot = AutoPilot.OFF;
		resolveThrusters();
	}
	public void rightThruster(boolean on) {
		my_rightOn = on;
		my_autoPilot = AutoPilot.OFF;
		resolveThrusters();
	}
	public void boost() {
		my_boostSinceMiss = true;
		my_boosts ++;
		
		if (my_boosts >= BOOSTS_PER_TIER[my_boostTier]) {
			my_boosts = 0;
			if (my_boostTier < BOOSTS_PER_TIER.length - 1) {
				my_boostTier++;
			}
			onNewTier(my_boostTier);
			
		} else {
		}
		
		my_yVelo += BOOST_SPEED;
		
		updateMinSpeed();
		if (my_yVelo < my_minSpeed) {
			my_yVelo = my_minSpeed;
		}
		
	}
	protected abstract void onNewTier(final int the_tier);
	
	public void missBoost() {
		my_boosts--;
		if (my_boosts < 0) {
			if (my_boostTier > 0) {
				if (my_boostSinceMiss) {
					my_boostSinceMiss = false;
				} else {
					my_boostTier--;
				}
			}
			my_boosts = 0;
		}
	}
	private void resolveThrusters() {
		if ((!my_leftOn && !my_rightOn) || (my_leftOn && my_rightOn)) {
			my_acceleration = 0;
		} else if (my_leftOn) {
			my_acceleration = -getSidewaysAcceleration();
		} else {
			my_acceleration = getSidewaysAcceleration();
		}
		 
	}
	
	public void moveTo(double the_target) {
		moveTo(the_target, 100, 1);
	}
	
	private void moveTo(double target_x, int percentMaxSpeed, int smoothness) {
		my_targetX = target_x + my_targetOffset;
		my_autoPilot = AutoPilot.POSITION;
		my_autoPilotSpeed = getSidewaysSpeed() / 100 * percentMaxSpeed;
		my_autoPilotSmoothness = smoothness;
	}
	public void touchUp() {
		my_targetOffset = 0;
	}
	
	/**
	 * Check to see if the ship is within the destruction of an envirolaser, and handle
	 * appropriately.
	 */
	public void checkEnviroLaser(final double the_left, final double the_width) {
		if (INVULNERABLE || !isAlive()) {
			return;
		}
		final double radius = the_width / 2;
		
		// how many radiuses the ship is from the center of the laser
		double ratio = (my_x - (the_left + radius)) / radius;
		if (Math.abs(ratio) < 1) {
			// the ship is within the radius of the laser, so this is a hit
			if (my_recoveryTime <= 0 && takeDamage(1)) {
				missBoost();
				missBoost();
				my_recoveryTime += RECOVERY_TIME;
			}
			
			my_xVelo = Math.signum(ratio) * (.7 + .3 * (1 - Math.abs(ratio))) * ENVIRO_LASER_IMPACT / .7;
		}
	}
	
	/**
	 * Collided with a block in such a way that the ship is thrown to the side.
	 */
	public boolean collidedSide() {
		if (my_recoveryTime <= 0 && collide(false)) {
			my_xVelo *= COLLISION_RATIO_X;
			return true;
		}
		return false;
	}
	
	
	public boolean collidedBottom() {
		return collide(true);
	}
	
	/**
	 * @param the_hitBottom
	 * @return whether the block should be destroyed
	 */
	private boolean collide(boolean the_hitBottom) {
		if (INVULNERABLE || my_recoveryTime > 0) {
			return true;
		}
		if (takeDamage(1)) {
			if (the_hitBottom) {
				my_yVelo *= COLLISION_RATIO_Y;
			}
			missBoost();
			missBoost();
			my_yVelo = my_minSpeed;
			my_recoveryTime += RECOVERY_TIME;
			return true;
		}
		return false;
	}
	/**
	 * @param the_damageTaken
	 * @return whether the ship actually took damage (false if invulnerable, for example.)
	 */
	private boolean takeDamage(final int the_damageTaken) {
		if (my_state != ShipState.ALIVE) {
			return true;
		}
		final boolean takeDamage;
		if (my_decoys.isEmpty()) {
			takeDamage = true;
		} else {
			if (my_decoyIndex >= my_decoys.size()) {
				// tried moving to all decoys; all of them would damage the ship.
				// move ship back to original position.
				my_x -= my_decoys.get(my_decoyIndex - 1).getXPosition();
				my_y -= my_decoys.get(my_decoyIndex - 1).getYPosition();
				takeDamage = true;
				my_decoyIndex = 0;
			} else {
				// the previous index damaged the ship.  move the ship back
				// to original position, then at the position of the current decoy.
				final ShipDecoy decoy = my_decoys.get(my_decoyIndex);
				if (my_decoyIndex > 0) {
					my_x -= my_decoys.get(my_decoyIndex - 1).getXPosition();
					my_y -= my_decoys.get(my_decoyIndex - 1).getYPosition();
				}
				my_x += decoy.getXPosition();
				my_y += decoy.getYPosition();
				takeDamage = false;
				my_decoyIndex++;
				my_safeFrames = 0;
			}
		}
		if (takeDamage) {
			if (getMaxHealth() - my_damage <= the_damageTaken) {
				System.out.println("Ship dying");
				my_state = ShipState.DYING;
				for (int i = 0; i < 3; i++) {
					final Explosion e = my_explosionPool.newObject();
					e.initialize(my_x + Math.random() * 30 - 15, my_y + Math.random() * 30 - 30, (Math.random() * .5 + .75) * 30);
					my_explosions.add(e);
				}
				updateMinSpeed();
				my_dyingTime = 500 + (int)(2900 * Math.min(1, (double) my_time / (5 * 1000 * 60)));
				my_recoveryTime = my_dyingTime;
			}
			my_damage += the_damageTaken;
			if (my_damage > getMaxHealth()) {
				my_damage = getMaxHealth();
			}
		}
		return takeDamage;
		
	}
	public int getHealth() {
		return getMaxHealth() - my_damage;
	}
	public abstract int getMaxHealth();
	
	
	
	
	public void repair(final int amount) {
		if (my_state == ShipState.DEAD) {
			return;
		}
		
		my_damage -= amount;
		
		if (my_state == ShipState.DYING) {
			my_state = ShipState.ALIVE;
			my_damage--;
			my_recoveryTime = RECOVERY_TIME;
		}
		if (my_damage < 0) {
			my_damage = 0;
		}
	}
	
	public void autoPilotOff() {
		my_autoPilot = AutoPilot.OFF;
	}
	
	public void render(GraphicsWrapper g) {
		
		g.saveMatrix();
		if (my_state != ShipState.DEAD) {
			
			int width = (int) (WIDTH * (1 - .15 * Math.abs(my_xVelo) / getSidewaysSpeed())); // was .6
			g.translate(getXPosition() - width / 2, my_y - HEIGHT / 2);
			
			if (my_image != null) {
				
				
				if (my_tints == null) {
					my_tints = new ImageTinter[TINTER_COUNT];
					for (int i = 0; i < TINTER_COUNT; i++) {
						int value = 255 * i / (TINTER_COUNT - 1);
						my_tints[i] = g.newImageTinter(new Color(255, value, value, 255));
					}
				}
				final int tint;
				if (my_recoveryTime > 0) {
					if (my_state == ShipState.DYING) {
						tint = (int)((my_tints.length - 1) * Math.min(1, (double)my_recoveryTime / my_dyingTime) / 2);
					} else {
						tint = (int)(my_tints.length * Math.max(0, 1 - (double)my_recoveryTime / RECOVERY_TIME));
					}
				} else {
					tint = my_tints.length - 1;
				}
				
				ImageTinter tinter = my_tints[tint];
				if (my_state == ShipState.DYING || my_recoveryTime % 400 < 275) {
					
					renderShip(g, width, tinter);
					
//					tinter.drawTintedImage(g, my_image, 0, 0);
				}
				
				
				if (my_decoyIndex == 0) {
					if (my_decoyTinter == null) {
						my_decoyTinter = g.newImageTinter(new Color(50, 50, 255, 60));
					}
					
					
					for (ShipDecoy s : my_decoys) {
						g.saveMatrix();
						g.translate(s.getXPosition(), s.getYPosition());
						renderShip(g, width, my_decoyTinter);
						g.restoreMatrix();
					}
				}
				
				
			} else {
				g.setColor(Color.GRAY);
				g.fillRect(0, 0, width, HEIGHT);
			}
			if (my_recoveryTime > 0) {
				final double ratio;
				if (my_state != ShipState.DYING) {
					g.setColor(Color.GREEN);
					g.fillOval(width / 2 - 2,  HEIGHT / 2 - 2, 4, 4);
				}
			}
			
		}
		g.restoreMatrix();
		try {
			for (Explosion e : my_explosions) {
				e.render(g);
			}
		} catch (ConcurrentModificationException e) {
			
		}
		
	}
	protected void addDecoy(ShipDecoy the_decoy) {
		if (my_decoys.size() >= MAX_DECOYS) {
			my_decoys.remove(my_decoys.size() - 1);
		}
		my_decoys.add(0, the_decoy);
	}
	protected int getDecoyCount() {
		return my_decoys.size();
	}
	protected void renderShip(GraphicsWrapper g, double width, ImageTinter tinter) {
		g.saveMatrix();
		g.scale((double) width / my_image.getWidth(), (double) WIDTH / my_image.getWidth());
		tinter.drawTintedImage(g, my_image, 0, 0);
		g.restoreMatrix();
	}
	protected abstract ImageWrapper getImage();
	protected abstract double getSidewaysSpeed();
	protected abstract double getSidewaysAcceleration();
	
	private void updateMinSpeed() {
		my_minSpeed = MIN_Y_SPEED;
		switch (my_state) {
		case DYING:
			my_minSpeed *= 2;
			// this drops through
		case ALIVE:
			my_minSpeed = my_minSpeed * (getHealth() == 1 ? 1.1 : 1) +
				(my_boosts + 10 * my_boostTier) * .05 / 10 + // was .05 
				Math.max(0,	Math.min(
						SPEED_INCREASES ? (MAX_SPEED - MIN_Y_SPEED) * my_time / 1000 / 60 / 5 : 0,
								MAX_SPEED))
			* (1 - (my_boostTier * 10 + my_boosts) * .005) ;
			
			double ratio = .6;
			ratio *= Math.pow(.85, my_boostTier);
			ratio *= Math.pow(.95, my_boosts);
			//my_minSpeed *= (.5 + ratio);
			break;
		case DEAD:
			my_minSpeed *= .6;
			break;
		}
		
	}
	public double getMinSpeed() {
		return my_minSpeed;
	}
	public boolean isAlive() {
		return my_state != ShipState.DEAD;
	}
	
	
	
	/**
	 * Used for the test user.
	 */
	public static void modifyMovementVar(MovementVars var, double multiplier) {
		if (var == null) {
			return;
		}
		switch(var) {
			case FORWARD_SPEED:
				MIN_Y_SPEED *= multiplier;
				break;
			case SIDEWAYS_SPEED:
				MAX_X_SPEED *= multiplier;
				break;
			case SIDEWAYS_ACCELERATION:
				ACCELERATION *= multiplier;
				break;
			case SIDEWAYS_DECELERATION:
				DECAY *= multiplier;
				break;		
		}
	}
	
	public static double getForwardSpeed() {
		return MIN_Y_SPEED;
	}
	public static void setForwardSpeed(final double speed) {
		MIN_Y_SPEED = speed;
	}
	public static void setSidewaysSpeed(final double speed) {
		MAX_X_SPEED = speed;
	}
	public static void setSidewaysAcceleration(final double a) {
		ACCELERATION = a;
	}
	public static void setDecay(final double decay) {
		DECAY = decay;
	}
	public static double getDecay() {
		return DECAY;
	}
	public static void setRecoveryTime(final double time) {
		RECOVERY_TIME = (int) time;
	}
	public static int getRecoveryTime() {
		return RECOVERY_TIME;
	}
	public static void setInvulnerable(boolean invulnerable) {
		INVULNERABLE = invulnerable;
	}
	public static void setSpeedDifficulty(boolean speedIncreases) {
		SPEED_INCREASES = speedIncreases;
	}
	

	public void setDesiredVelocityRatio(final double the_ratio) {
		my_autoPilot = AutoPilot.VELOCITY;
		double ratio = the_ratio;
		if (ratio > 1) {
			ratio = 1;
		} else if (ratio < -1) {
			ratio = -1;
		}
		my_targetVelocity = ratio * getSidewaysSpeed();
	}
	
	

	@Override
	public Explosion createObject() {
		return new Explosion();
	}

	@Override
	public double getXAcceleration() {
		return my_acceleration;
	}

	@Override
	public int getTime() {
		// TODO Auto-generated method stub
		return my_time;
	}

	@Override
	public int getBoostCount() {
		// TODO Auto-generated method stub
		return my_boosts;
	}
	
	@Override
	public void missPowerUp(final PowerUp the_type) {}
	@Override
	public void hitPowerUp(final PowerUp the_powerUp) {
		switch(the_powerUp.getType()) {
		case SPEED:
			boost();
			break;
		case REPAIR:
			repair(1);
			break;
		case DECOY:
			addDecoy(new ShipDecoy(30, 0));
			addDecoy(new ShipDecoy(-30, 0));
			break;
		}
	}

}
